package org.metasketch.demo.diagrams.PSD;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.input.MouseEvent;

import org.metasketch.demo.diagrams.EmptyDiagramBuilder;
import org.metasketch.demo.diagrams.ATD.ActorRoleNode;
import org.metasketch.demo.model.ActorRole;
import org.metasketch.demo.model.CoordinationLink;
import org.metasketch.demo.model.CoordinationStep;
import org.metasketch.demo.model.CoordinationStepKind;
import org.metasketch.demo.model.ProductionStep;
import org.metasketch.demo.model.TransactionKind;
import org.metasketch.demo.model.TransactionStep;
import org.metasketch.diagram.javafx.graphics.BaseGraphic;
import org.metasketch.diagram.javafx.graphics.BaseLink;
import org.metasketch.editor.links.DefaultLinkHandler;
import org.metasketch.editor.links.LinkEndOrientation;
import org.metasketch.editor.links.LinkEndType;
import org.metasketch.editor.links.SemanticConfig;
import org.metasketch.editor.links.SemanticLinkConfig;
import org.metasketch.editor.timeline.SnapshotManager;
import org.metasketch.editor.toolbox.BaseDiagramType;
import org.metasketch.editor.toolbox.DefaultDiagramBuilder;
import org.metasketch.editor.toolbox.DefaultSelectHandler;
import org.metasketch.editor.toolbox.DefaultToolHandler;
import org.metasketch.editor.toolbox.IDiagramType;
import org.metasketch.editor.toolbox.IToolHandler;
import org.metasketch.editor.toolbox.InheritedRelation;
import org.metasketch.editor.toolbox.SimpleToolBaseDiagramType;
import org.metasketch.editor.toolbox.advanced.IRealDropHandler;
import org.metasketch.graphics.Diagram;
import org.metasketch.graphics.Point;
import org.metasketch.ham.diagrams.ActivityRelationshipLink;
import org.metasketch.ham.diagrams.HumanParticipationUnitNode;
import org.metasketch.ham.model.HumanActivityElement;
import org.metasketch.ham.model.HumanActivityGroup;
import org.metasketch.ham.model.HumanActivityRelationLink;
import org.metasketch.ham.model.HumanParticipationUnit;
import org.metasketch.javafx.graphics.IJFXGraphicNode;
import org.metasketch.javafx.graphics.JFXDiagram;
import org.metasketch.model.BaseModel;
import org.metasketch.model.CommonIdentified;

public class PSDDiagram extends SimpleToolBaseDiagramType implements
		IRealDropHandler {

	private int defaultHGap = 10;
	private int defaultVGap = 40;
	private int defaultSizeGap = 120;

	public PSDDiagram() {
		super("Process Structure Diagram", new DefaultDiagramBuilder());
		// this.diagramBuilder.set
		registerTool("Select", null, "Select", new DefaultSelectHandler(), null);

		registerToolClasses("Actor Role", null, "ActorRole-PSD-Node",
				ActorRole.class, ActorRoleResponsibilityNode.class, true);

		registerToolClasses("Coordination Step", null,
				"CoordinationStep-PSD-Node", CoordinationStep.class,
				CoordinationStepNode.class, false);

		registerToolClasses("Production Step", null, "ProductionStep-PSD-Node",
				ProductionStep.class, ProductionStepNode.class, false);

		registerToolClasses(
				"Decline",
				null,
				"Decline-Coordination-Step-PSD-Node",
				CoordinationStep.class,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Decline);",
				CoordinationStepNode.class, true);

		registerToolClasses(
				"Reject",
				null,
				"Reject-Coordination-Step-PSD-Node",
				CoordinationStep.class,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Reject);",
				CoordinationStepNode.class, true);

		registerToolClasses(
				"Cancel",
				null,
				"Cancel-Coordination-Step-PSD-Node",
				CoordinationStep.class,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Cancel);",
				CoordinationStepNode.class, true);

		registerToolClasses(
				"Refuse",
				null,
				"Refuse-Coordination-Step-PSD-Node",
				CoordinationStep.class,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Refuse);",
				CoordinationStepNode.class, true);

		registerToolClasses(
				"Stop",
				null,
				"Stop-Coordination-Step-PSD-Node",
				CoordinationStep.class,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Stop);",
				CoordinationStepNode.class, true);

		registerToolClasses(
				"Allow",
				null,
				"Allow-Coordination-Step-PSD-Node",
				CoordinationStep.class,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Allow);",
				CoordinationStepNode.class, true);

		{
			SemanticLinkConfig linkConfigB = new SemanticLinkConfig();
			linkConfigB.setInherited(true);

			linkConfigB.setSourceTypes(TransactionStep.class);
			linkConfigB.getSource().getLinkEnd()
					.setOrientation(LinkEndOrientation.CLOSEST);

			linkConfigB.setTargetTypes(TransactionStep.class);
			linkConfigB.getTarget().getLinkEnd()
					.setOrientation(LinkEndOrientation.CLOSEST);
			linkConfigB.getTarget().getLinkEnd()
					.setGraphic(LinkEndType.FILLEDTRIANGLE);

			SemanticConfig cfg = new SemanticConfig();
			cfg.setSemanticType(CoordinationLink.class);
			// cfg.setSemanticFactory(new HumanActivityRelationTypeFactory(
			// HumanActivityRelationType.PRECEDES));
			// cfg.setSemanticBuildScript("import org.metasketch.ham.model.*; semantic.setType(HumanActivityRelationType.PRECEDES);");
			cfg.setLinkSourceFieldname("source");
			cfg.setLinkTargetFieldname("target");
			// cfg.setSemanticTestScript("import org.metasketch.ham.model.*; return semantic.getType() == HumanActivityRelationType.PRECEDES;");

			registerSemanticLink("coordination link", "Coordination-PSD-Link",
					CoordinationLinkNode.class, cfg, linkConfigB);
		}

		addSemanticalMenuEntry(
				CoordinationLink.class,
				"Reverse",
				null,
				null,
				"semantic.source = targetSemantic; semantic.target = sourceSemantic; ",
				"graphic.source = targetGraphic; graphic.target = sourceGraphic; ");

		addSemanticalMenuEntry(CoordinationLink.class, "Switch Conditional",
				"semantic.conditional = !semantic.conditional;");

		String testScript = "import org.metasketch.demo.model.*; if(semantic.kind == CoordinationStepKind.Request || semantic.kind == CoordinationStepKind.Promise || semantic.kind == CoordinationStepKind.State || semantic.kind == CoordinationStepKind.Accept) return false; else return true;";

		addSemanticalMenuEntry(
				CoordinationStep.class,
				"Change to Decline",
				testScript,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Decline); ");
		addSemanticalMenuEntry(
				CoordinationStep.class,
				"Change to Reject",
				testScript,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Reject); ");
		addSemanticalMenuEntry(
				CoordinationStep.class,
				"Change to Cancel",
				testScript,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Cancel); ");
		addSemanticalMenuEntry(
				CoordinationStep.class,
				"Change to Refuse",
				testScript,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Refuse); ");
		addSemanticalMenuEntry(
				CoordinationStep.class,
				"Change to Stop",
				testScript,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Stop); ");
		addSemanticalMenuEntry(
				CoordinationStep.class,
				"Change to Allow",
				testScript,
				"import org.metasketch.demo.model.*; semantic.setKind(CoordinationStepKind.Allow); ");

		relationManager.register(new InheritedRelation(TransactionStep.class,
				ActorRole.class, "actorRole", null));

		addDefaultDeleteFromDiagramInheritedPopupMenuOption(TransactionStep.class);

		addDefaultDeleteFromDiagramInheritedPopupMenuOption(ActorRole.class);

		addDefaultDeleteFromDiagramInheritedPopupMenuOption(CoordinationLink.class);

		dropHandlerRegistry.register(TransactionKind.class, this);
		dropHandlerRegistry.registerInherited(TransactionStep.class, this);
		// dropHandlerRegistry.register(ProductionStep.class, this);

	}

	@Override
	public ToolBar createToolbar() {

		ToolBar toolBar = new ToolBar(getTypeButton("Select"),

		new Label("Steps"),
				getTypeButton("Decline-Coordination-Step-PSD-Node"),
				getTypeButton("Reject-Coordination-Step-PSD-Node"),
				getTypeButton("Cancel-Coordination-Step-PSD-Node"),
				getTypeButton("Refuse-Coordination-Step-PSD-Node"),
				getTypeButton("Stop-Coordination-Step-PSD-Node"),
				getTypeButton("Allow-Coordination-Step-PSD-Node"), new Label(
						"Links"), getTypeButton("Coordination-PSD-Link"));

		toolBar.setOrientation(Orientation.VERTICAL);

		return toolBar;
	}

	@Override
	public void handleDrop(SimpleToolBaseDiagramType diagramType,
			CommonIdentified semantic, Object dropTarget, MouseEvent event,
			JFXDiagram jfxDiagram, BaseModel model) {

		try {

			if (semantic instanceof TransactionStep) {

				TransactionStep step = (TransactionStep) semantic;

				TransactionKind transaction = step.getParentTransaction();

				ActorRole role = step.getActorRole();

				ActorRoleResponsibilityNode arNode = (ActorRoleResponsibilityNode) dropTarget;
				ActorRole dropRole = (ActorRole) arNode.getSemantic();

				if (transaction == null || role == null) {
					handleDrop(semantic, dropTarget, event, jfxDiagram, model);
					return;
				}

				if (!transaction.getExecutedBy().contains(dropRole)
						&& !transaction.getInitiatedBy().contains(dropRole)) {
					// can't change the actor role to one that is not the
					// initiator nor the executor of the transaction !!!!
					// this !!!
					// TODO show error
					return;
				}

			} else if (semantic instanceof TransactionKind) {

				TransactionKind transaction = (TransactionKind) semantic;

				// the transaction needs to have defined the executer and the
				// initiator
				if (transaction.getExecutedBy().size() == 0
						|| transaction.getInitiatedBy().size() == 0)
					return;

				DefaultToolHandler actorRoleTool;

				actorRoleTool = (DefaultToolHandler) toolHandlerRegistry
						.get("ActorRole-PSD-Node");

				// gotta have a tool for creating actor roles
				if (actorRoleTool == null)
					return;

				DefaultToolHandler coordinationStepTool = (DefaultToolHandler) toolHandlerRegistry
						.get("CoordinationStep-PSD-Node");

				// gotta have a tool for creating coordination Steps
				if (coordinationStepTool == null)
					return;

				// production step
				DefaultToolHandler productionStepTool = (DefaultToolHandler) toolHandlerRegistry
						.get("ProductionStep-PSD-Node");

				// gotta have a tool for creating production Steps
				if (productionStepTool == null)
					return;

				DefaultLinkHandler coordinationLinkTool = (DefaultLinkHandler) toolHandlerRegistry
						.get("Coordination-PSD-Link");

				// gotta have a tool for creating coordination links
				if (coordinationLinkTool == null)
					return;

				ActorRole executor = transaction.getExecutedBy().iterator()
						.next();
				ActorRole initiator = transaction.getInitiatedBy().iterator()
						.next();

				// confirm all of the steps have only the executor and initiator
				// as
				// actor roles.
				Set<TransactionStep> steps = transaction.getSteps();
				for (TransactionStep step : steps) {
					ActorRole role = step.getActorRole();
					if (role == null || (role != executor && role != initiator)) {
						throw new Exception(
								"Transaction Step "
										+ step.getId()
										+ " of transaction \""
										+ transaction.getName()
										+ "\" ("
										+ transaction.getId()
										+ ") is processed by another Actor Role "
										+ step.getActorRole().getName()
										+ " ("
										+ step.getActorRole().getId()
										+ ") that not the transaction's executor nor initiator !!!");
						// return;
					}
				}

				BaseGraphic executerNode = null;
				BaseGraphic initiatorNode = null;

				executerNode = jfxDiagram.getClosestNode(new Point(0, 0),
						executor);
				initiatorNode = jfxDiagram.getClosestNode(new Point(0, 0),
						initiator);

				TransactionStep request, promise, state, accept, production = null;

				request = transaction
						.getFirstCoordinationStep(CoordinationStepKind.Request);
				promise = transaction
						.getFirstCoordinationStep(CoordinationStepKind.Promise);

				production = transaction.getProductionStep();

				state = transaction
						.getFirstCoordinationStep(CoordinationStepKind.State);
				accept = transaction
						.getFirstCoordinationStep(CoordinationStepKind.Accept);

				if (request == null || promise == null || production == null
						|| state == null || accept == null)
					throw new Exception("transaction \""
							+ transaction.getName()
							+ "\" doesn't have all of the required steps !!!");

				BaseGraphic requestNode = null, promiseNode = null, productionNode = null, stateNode = null, acceptNode = null;

				if (executerNode != null) {
					// TODO test for contained steps
					List<IJFXGraphicNode> nodes = executerNode
							.getGraphicNodeSemanticClass(TransactionStep.class);
					for (IJFXGraphicNode node : nodes) {
						semantic = node.getSemantic();
						if (semantic == promise) {
							promiseNode = (BaseGraphic) node;
						} else if (semantic == production) {
							productionNode = (BaseGraphic) node;
						} else if (semantic == state) {
							stateNode = (BaseGraphic) node;
						}
					}
				}

				if (initiatorNode != null) {
					// TODO test for contained steps
					List<IJFXGraphicNode> nodes = initiatorNode
							.getGraphicNodeSemanticClass(TransactionStep.class);
					for (IJFXGraphicNode node : nodes) {
						semantic = node.getSemantic();
						if (semantic == request) {
							requestNode = (BaseGraphic) node;
						} else if (semantic == accept) {
							acceptNode = (BaseGraphic) node;

						}

					}
				}

				// TODO test for graphical nodes of transaction steps..if any of
				// them exist...don't create anything !!!

				Point initiatorCoords = null;
				if (initiatorNode == null) {
					initiatorCoords = new Point((int) event.getX(),
							(int) event.getY());
					initiatorNode = (BaseGraphic) actorRoleTool
							.treatCreationEvent(initiatorCoords, event,
									jfxDiagram, model, dropTarget, initiator,
									false);
				} else
					initiatorCoords = new Point(initiatorNode.getX(),
							initiatorNode.getY());

				Point executerCoords = null;
				if (executerNode == null) {
					executerCoords = new Point(initiatorNode.getX() + 200,
							initiatorNode.getY());

					executerNode = (BaseGraphic) actorRoleTool
							.treatCreationEvent(executerCoords, event,
									jfxDiagram, model, dropTarget, executor,
									false);
				} else
					executerCoords = new Point(executerNode.getX(),
							executerNode.getY());

				// create main transaction steps - only
				// Set<TransactionStep> steps = transaction.getSteps();

				Point requestPoint = null;
				if (requestNode == null)
					requestPoint = new Point(defaultHGap, defaultVGap);

				Point promisePoint = null;
				if (promiseNode == null)
					promisePoint = new Point(defaultHGap, defaultVGap);

				Point productionPoint = null;
				if (productionNode == null)
					productionPoint = new Point(defaultHGap, defaultSizeGap
							+ defaultVGap);

				Point statePoint = null;
				if (stateNode == null)
					statePoint = new Point(defaultHGap, defaultSizeGap * 2
							+ defaultVGap);

				Point acceptPoint = null;
				if (acceptNode == null)
					acceptPoint = new Point(defaultHGap, defaultSizeGap * 2
							+ defaultVGap);

				for (TransactionStep step : steps) {
					if (step.getActorRole() == null) {
						throw new Exception("Transaction Step " + step.getId()
								+ " of transaction \"" + transaction.getName()
								+ "\" (" + transaction.getId()
								+ ") has not actor role defined !!!");
					} else if (step.getActorRole() == initiator) {
						// add to initiatorNode
						if (step == request && requestNode == null) {
							requestNode = (BaseGraphic) coordinationStepTool
									.treatCreationEvent(requestPoint, event,
											jfxDiagram, model, initiatorNode,
											step, false);
						} else if (step == accept && acceptNode == null) {
							acceptNode = (BaseGraphic) coordinationStepTool
									.treatCreationEvent(acceptPoint, event,
											jfxDiagram, model, initiatorNode,
											step, false);
						}
					} else if (step.getActorRole() == executor) {
						// add to executorNode
						if (step == promise && promiseNode == null) {
							promiseNode = (BaseGraphic) coordinationStepTool
									.treatCreationEvent(promisePoint, event,
											jfxDiagram, model, executerNode,
											step, false);
						} else if (step == production && productionNode == null) {
							productionNode = (BaseGraphic) productionStepTool
									.treatCreationEvent(productionPoint, event,
											jfxDiagram, model, executerNode,
											step, false);
						} else if (step == state && stateNode == null) {
							stateNode = (BaseGraphic) coordinationStepTool
									.treatCreationEvent(statePoint, event,
											jfxDiagram, model, executerNode,
											step, false);
						}
					} else {
						throw new Exception(
								"Transaction Step "
										+ step.getId()
										+ " of transaction \""
										+ transaction.getName()
										+ "\" ("
										+ transaction.getId()
										+ ") is processed by another Actor Role "
										+ step.getActorRole().getName()
										+ " ("
										+ step.getActorRole().getId()
										+ ") that not the transaction's executor nor initiator !!!");
					}
				}

				// create links between step nodes

				CoordinationLink firstLink = request.getFirstLinkTo(promise);

				CoordinationLink secondLink = promise
						.getFirstLinkTo(production);

				CoordinationLink thirdLink = production.getFirstLinkTo(state);

				CoordinationLink fourthLink = state.getFirstLinkTo(accept);

				if (firstLink == null || secondLink == null
						|| thirdLink == null || fourthLink == null)
					throw new Exception(
							"transaction \""
									+ transaction.getName()
									+ "\" ("
									+ transaction.getId()
									+ ") doesn't have all of the valid links between steps !!!");

				BaseLink firstLinkNode = jfxDiagram.getClosestLink(
						initiatorCoords, firstLink);
				if (firstLinkNode == null)
					coordinationLinkTool.buildLink(requestNode, promiseNode,
							jfxDiagram, model, firstLink, false);

				BaseLink secondLinkNode = jfxDiagram.getClosestLink(
						initiatorCoords, secondLink);
				if (secondLinkNode == null)
					coordinationLinkTool.buildLink(promiseNode, productionNode,
							jfxDiagram, model, secondLink, false);

				BaseLink thirdLinkNode = jfxDiagram.getClosestLink(
						initiatorCoords, thirdLink);
				if (thirdLinkNode == null)
					coordinationLinkTool.buildLink(productionNode, stateNode,
							jfxDiagram, model, thirdLink, false);

				BaseLink fourthLinkNode = jfxDiagram.getClosestLink(
						initiatorCoords, fourthLink);
				if (fourthLinkNode == null)
					coordinationLinkTool.buildLink(stateNode, acceptNode,
							jfxDiagram, model, fourthLink, false);

				// create links between any other steps that exist in the
				// diagram...reflect the model !!!! Useful for transaction
				// composition analysis...

				List<BaseGraphic> actorRoleNodes = jfxDiagram
						.getGraphicNodeSemanticClass(ActorRole.class);
				for (BaseGraphic actorRoleNode : actorRoleNodes) {
					List<IJFXGraphicNode> stepNodes = actorRoleNode
							.getGraphicNodeSemanticClass(TransactionStep.class);
					for (IJFXGraphicNode stepNode : stepNodes) {
						TransactionStep step = (TransactionStep) stepNode
								.getSemantic();
						if (step instanceof CoordinationStep) {

							CoordinationStep coordStep = (CoordinationStep) step;

							// test for links....
							if (coordStep.getKind() == CoordinationStepKind.Promise
									|| coordStep.getKind() == CoordinationStepKind.Accept) {

								Set<CoordinationLink> links = coordStep
										.getOwnedCoordinationLinks();
								for (CoordinationLink link : links) {

									TransactionStep targetStep = link
											.getTarget();
									// use the point of the StepNode
									BaseGraphic targetStepNode = jfxDiagram
											.getClosestNode(new Point(0, 0),
													targetStep);
									if (targetStepNode == null) {
										continue;
									}

									BaseLink linkNode = jfxDiagram
											.getClosestLink(new Point(0, 0),
													link);
									if (linkNode == null)
										coordinationLinkTool.buildLink(
												(BaseGraphic) stepNode,
												targetStepNode, jfxDiagram,
												model, link, false);

								}

							}

						}
					}
				}

				// take snapshot
				SnapshotManager.takeSnapshot();

			}
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}
